package mop.model.resource;

import java.sql.Timestamp;
import java.util.LinkedList;
import java.util.List;

import mop.exception.IllegalDescriptionException;
import mop.exception.IllegalDuplicationException;
import mop.exception.IllegalIdException;
import mop.exception.IllegalReservationException;
import mop.exception.IllegalReservationScheduleException;
import mop.exception.UnavailableResourceException;
import mop.model.AbstractModelWithDescription;
import mop.model.DataBase;
import mop.model.task.Task;


/**
 * Abstract class Resource.
 *  
 * @invar has no double reservations
 * 			|for(Reservation r1 : reservations)
 * 			| for(Reservation r2 : reservations)
 * 			|  if(!r1.equals(r2)) !r1.isDuring(r2)
 * 
 * @author	MOP Team 7
 */
public  class Resource extends AbstractModelWithDescription implements ResourceInterface
{
	/**
	 * Resource Constructor
	 * 
	 * @param 	rt 					Resource type
	 * @param 	description			The description of this object.
	 * @param	resourceDatabase	DataBase object containing all resources.
	 * 
	 * @effect super constructor is called
	 * 	|super(id,description)
	 * 
	 * @effect resource is added to resource database
	 * 
	 * @post	Resource database reference is set.
	 * @post	Reservations is set to an empty list.
	 * 
	 * @throws 	IllegalDescriptionException 
	 * 			@see AbstractModelWithDescription#AbstractModelWithDescription(String, String)
	 * @throws	IllegalDuplicationException
	 * 			Thrown when a resource with the given description already exists. 
	 * @throws IllegalIdException 
	 * 			@see AbstractModel#AbstractModel(String)
	 */
	public Resource(ResourceType rt,String id, String description, DataBase<Resource> resourceDatabase) 
		throws IllegalDescriptionException, IllegalDuplicationException, IllegalIdException
	{
		super(id,description);
		this.resourceDatabase = resourceDatabase;
		this.rt = rt;
		
		// Add self to database.
		this.resourceDatabase.add(this);
				
		this.reservations = new LinkedList<Reservation>();
	}
	
	/**
	 * Create resource with name as identifier
	 * @effect call twin constructor with name as identifier
	 * @see this(ResourceType, String, String, DataBase)
	 */
	public Resource(ResourceType rt, String description, DataBase<Resource> resourceDatabase) throws IllegalDescriptionException, IllegalDuplicationException, IllegalIdException{
		this(rt,description,description,resourceDatabase);
	}
	
	/***************************************************************************
	 * 						Model interface
	 ***************************************************************************/
	
	/**
	 * get a reference to the interface
	 * @return this
	 */
	public ResourceInterface getInterface()
	{
		return this;
	}
	
	/***************************************************************************
	 * 							Project database
	 ***************************************************************************/
	
	private DataBase<Resource> resourceDatabase;
	
	
	/***************************************************************************
	 * 						     Reservation
	 ***************************************************************************/
	
	/**
	 * Adds a reservation for this resource.
	 * 
	 * @param 	start		The start time as a Timestamp.
	 * @param	duration	The duration time as a Timestamp.
	 * 
	 * @post	A reservation for the resource is made (added to reservations list).
	 * 			| reservations.contains( new Reservation(start, duration) )
	 * 
	 * @throws	IllegalReservationException
	 * 			If the tasks reservation constraints don't allow this reservation
	 * @throws	UnavailableResourceException 
	 * 			If the resource is unavailable at that time.
	 * @throws IllegalReservationScheduleException 
	 * 			If task schedule doesn't allow this reservation
	 */
	public void addReservation(Task task, Timestamp start, Timestamp duration) 
		throws IllegalReservationException, UnavailableResourceException, IllegalReservationScheduleException
	{
		Reservation r = new Reservation(this, start, duration);
		
		if(!this.isAvailable(r.getStart(), r.getEnd()))
			throw new UnavailableResourceException(this, start, duration);
		
		task.addReservation(r);
		this.reservations.add(r);
	}
	
	/**
	 * Returns a list of reservations for this Resource.
	 * 
	 * @return	A list of Reservation objects.
	 */
	public List<Reservation> getReservations()
	{
		return this.reservations;
	}
	
	/**
	 * Returns a copy of the list of reservations for this Resource.
	 * 
	 * @return	A copy of the list of Reservation objects.
	 */
	public List<ReservationInterface> getReservationsList()
	{
		List<ReservationInterface> r = new LinkedList<ReservationInterface>();
		r.addAll(reservations);
		return r;
	}

	
	/**
	 * Checks whether this Resource can be reserved between a given time period or not.
	 * 
	 * @param	start	A time stamp.
	 * @param	end		A time stamp.
	 * 
	 * @return	True if the Resource is available, otherwise false.
	 */
	public boolean isAvailable(Timestamp start, Timestamp end)
	{
		for(Reservation r : this.getReservations())
			if( r.isDuring(start, end) )
				return false;
		
		return true;
	}
	
	/**
	 * Removes reservation from reservations list
	 * 
	 * @param reservation	Reservation to remove
	 * 
	 * @post Reservation removed from reservation list
	 * 	|reservations.contains(reservation) == false
	 */
	public void removeReservation(Reservation reservation) {
		reservations.remove(reservation);
	}
	
	/**
	 * List of reservations of this resource.
	 */
	private LinkedList<Reservation> reservations;
	
	
	/***************************************************************************
	 * 						Resource Type
	 ***************************************************************************/
	
	/**
	 * Get the interface to the current Resource Type
	 * 
	 * @return interface to resource type
	 */
	public ResourceTypeInterface getResourceType()
	{
		return rt.getInterface();
	}
	
	/** the type of the resource */
	private ResourceType rt = null;



	
}